﻿using System.Collections.Generic;
using System.Linq;
using Ast.Framework.Caching;
using Ast.Framework.Environment.Extensions.Folders;
using Ast.Framework.Environment.Extensions.Loaders;
using Ast.Framework.Environment.Extensions.Models;
using Ast.Framework.FileSystems.Dependencies;
using Ast.Framework.Utility.Extensions;
using Castle.Core.Logging;

namespace Ast.Framework.Environment.Extensions
{
    public interface IExtensionManager
    {
        IEnumerable<ExtensionDescriptor> AvailableExtensions();

        ExtensionDescriptor GetExtension(string id);

        ExtensionEntry LoadExtension(string id);
    }

    public class ExtensionManager : IExtensionManager
    {
        private readonly IEnumerable<IExtensionFolders> _folders;
        private readonly IAsyncTokenProvider _asyncTokenProvider;
        private readonly IDependenciesFolder _dependenciesFolder;
        private readonly ICacheManager _cacheManager;
        private readonly IParallelCacheContext _parallelCacheContext;
        private readonly IEnumerable<IExtensionLoader> _loaders;

        public ExtensionManager(IEnumerable<IExtensionFolders> folders, IEnumerable<IExtensionLoader> loaders, ICacheManager cacheManager, IParallelCacheContext parallelCacheContext, IAsyncTokenProvider asyncTokenProvider, IDependenciesFolder dependenciesFolder)
        {
            _folders = folders;
            _asyncTokenProvider = asyncTokenProvider;
            _dependenciesFolder = dependenciesFolder;
            _cacheManager = cacheManager;
            _parallelCacheContext = parallelCacheContext;
            _loaders = loaders.OrderBy(x => x.Order).ToArray();

            Logger = NullLogger.Instance;
        }

        public ILogger Logger { get; set; }

        public IEnumerable<ExtensionDescriptor> AvailableExtensions()
        {
            return _cacheManager.Get("AvailableExtensions", ctx => _parallelCacheContext
                .RunInParallel(_folders, folder => folder.AvailableExtensions().ToList())
                .SelectMany(descriptors => descriptors)
                .ToReadOnlyCollection());
        }

        public ExtensionDescriptor GetExtension(string id)
        {
            return AvailableExtensions().FirstOrDefault(x => x.Id == id);
        }

        public ExtensionEntry LoadExtension(string id)
        {
            var extensionDescriptor = GetExtension(id);
            return _cacheManager.Get(id, ctx =>
            {
                var entry = BuildEntry(extensionDescriptor);
                if (entry != null)
                {
                    ctx.Monitor(_asyncTokenProvider.GetToken(monitor =>
                    {
                        foreach (var loader in _loaders)
                        {
                            loader.Monitor(entry.Descriptor, monitor);
                        }
                    }));
                }
                return entry;
            });
        }

        private ExtensionEntry BuildEntry(ExtensionDescriptor descriptor)
        {
            foreach (var loader in _loaders)
            {
                var dependency = _dependenciesFolder.GetDescriptor(descriptor.Id);
                if (dependency != null && dependency.LoaderName == loader.Name)
                {
                    return loader.Load(descriptor);
                }
            }

            Logger.WarnFormat("No suitable loader found for extension \"{0}\"", descriptor.Id);
            return null;
        }
    }
}